# -*- coding: utf-8 -*-

import paho.mqtt.client as mqtt
import os
import time
import hashlib
import serial
import re
import sys
import subprocess
import threading
import signal
import logging
from multiprocessing import Process
import json
import socket
import struct
import fcntl
import asyncio
import requests
from serial.tools import list_ports


def sigint_handler(signum, frame):
    global is_sigint_up
    is_sigint_up = True


signal.signal(signal.SIGINT, sigint_handler)

is_sigint_up = False


class mqttRun():
    def __init__(self):
        self.IDLE = 0
        self.RUN = 1
        self.state = self.IDLE
        self.MD5 = None
        self.TestMode = "lua"
        # /*+\new\czm\2021.06.03\绑定测试设备imei和8955支付狗的imei*/
        self.deviceInfo = {
            "862167050537699": {
                "DogImei": 123654,
                "DeviceAT": ""
            },
            "862167050537145": {
                "DogImei": 123654,
                "DeviceAT": ""
            },
            "866714046612692": {
                "DogImei": 123654,
                "DeviceAT": ""
            },
            "862167050535131": {
                "DogImei": 123654,
                "DeviceAT": ""
            },
            "866714049394256": {
                "DogImei": 123654,
                "DeviceAT": ""
            },
            "862167053252114": {
                "DogImei": 123654,
                "DeviceAT": ""
            },
            "862167050544927": {
                "DogImei": 123654,
                "DeviceAT": ""
            },
            "862167053250001": {
                "DogImei": 123654,
                "DeviceAT": ""
            }
        }
        # /*-\new\czm\2021.06.03\绑定测试设备imei和8955支付狗的imei*/
        self.NowTestVer = False
        self.diag = "./diag"
        self.setupUsb = "/home/airm2m/AIRM2M_TEST_PUB/multiTesting/setup.sh"
        self.localSocket = "enp3s0"
        self.executeTest = None
        self.testVer = ""
        self.testWeb = False
        self.NowTestStartTimer = 0
        self.TesterPath = "/home/airm2m/AIRM2M_TEST_PUB/multiTesting/"
        self.sprd_dloader = "/home/airm2m/AIRM2M_TEST_PUB/rda_linux_download/LinuxDownloadSource/Bin/sprd_dloader/DLoader -pac recVer.pac -dev "
        self.logger = logging.getLogger('serialmanage')
        self.logger.setLevel(level=logging.INFO)
        console = logging.StreamHandler()
        formatter = logging.Formatter(
            fmt='%(asctime)s.%(msecs)03d line:%(lineno)d %(levelname)s %(message)s', datefmt='%Y-%m-%d %X')
        console.setFormatter(formatter)
        console.setLevel(logging.INFO)
        self.logger.addHandler(console)
        self.testInfoDict = {
            "imei": "",
            "status": "",
            "htmlData": ""
        }
        os.system(self.setupUsb)
        pass

    def getmd5(self, file):
        m = hashlib.md5()
        with open(file, 'rb') as f:
            for line in f:
                m.update(line)
        md5code = m.hexdigest()
        self.logger.info(md5code)
        return md5code

    def set_mode_download(self, dev):
        isexit = False
        # /*+\new\czm\2021.06.02\这个地方概率性会导致库里面报错，直接注释掉*/
        # for com in list_ports.comports():
        #     if com[0].find(dev) != -1 :
        #         isexit = True
        # if isexit:
        # /*-\new\czm\2021.06.02\这个地方概率性会导致库里面报错，直接注释掉*/
        try:
            ios = serial.Serial(dev, baudrate=115200, xonxoff=0, timeout=0)
            self.logger.info("send AT*DOWNLOAD=1")
            ios.write(("AT*DOWNLOAD=1\r\n").encode("gbk"))
            ios.close()
        except Exception as e:
            self.logger.warning(e)
            if ios:
                ios.close()
    # /*+\new\czm\2021.06.03\下载结束后,对每个设备分别发at获取设备的imei*/
    # 填入self.deviceInfo[imeiStr]["DeviceAT"]
    # 保存当前设备的接入端口号，以便判断设备是不是正常开机接入
    def get_imei(self,dev):
        try:                    
            ios = serial.Serial(dev, baudrate=115200, xonxoff=0, timeout=0)
            self.logger.info("send AT+WIMEI?")
            ios.write(("AT+WIMEI?\r\n").encode("gbk"))
            while True:
                data = ios.readline()
                data = data.decode("utf-8")
                if "+WIMEI:" in data:
                    imeiStr = data[len("+WIMEI: "):-2]
                    print(imeiStr)
                    self.deviceInfo[imeiStr]["DeviceAT"]=dev
                    break
            ios.close()
        except Exception as e:
            self.logger.warning(e)
            if ios :
                ios.close()
    # /*-\new\czm\2021.06.03\下载结束后,对每个设备分别发at获取设备的imei*/

    def FindNewerFile(self, path, key):
        dict = []
        for root, dirs, files in os.walk(path):
            for file in dirs:
                full_path = os.path.join(root, file)
                tempMatch = re.match("(.*)_86.*_(.*)", full_path)
                if tempMatch:
                    nowTime = time.mktime(time.strptime(
                        "2020:%s" % tempMatch.group(2), "%Y:%m:%d:%H:%M:%S"))
                    if(nowTime > self.NowTestStartTimer):
                        if os.path.exists(full_path+"//%s" % key):
                            dict.append(full_path+"//%s" % key)
        if len(dict) != 0:
            return dict
        else:
            return False

    def get_log_size(self, path):
        totalSize = 0
        key = False
        if self.TestMode == "at":
            key = "log_"
        elif self.TestMode == "lua":
            key = "stdout"
        if key:
            for root, dirs, files in os.walk(path):
                for file in files:
                    full_path = os.path.join(root, file)
                    if full_path.find(key) != -1:
                        totalSize = totalSize + os.path.getsize(full_path)
        return totalSize

    def get_ip(self, ifname):
        s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
        return socket.inet_ntoa(fcntl.ioctl(
            s.fileno(),
            0x8915,  # SIOCGIFADDR
            struct.pack('256s', bytes(ifname[:15], 'utf-8'))
        )[20:24])

    def analysisData(self, resultList):
        self.logger.info("analysisData")
        errList = []
        for result in resultList:
            if result.find("[E]") != -1:
                res = re.match("(.*): (.*)", result)
                if res:
                    errList.append([res.group(1), res.group(2)])
        return errList

    def DatacombinHtml(self, resultList):
        if self.TestMode == "lua":
            dicts = self.analysisData(resultList)
            if dicts:
                from HTMLTable import (
                    HTMLTable,
                )
                table = HTMLTable(caption='TEST ERROR')
                table.append_header_rows((('TIME', 'ERR MESSAGE'),))
                for i in dicts:
                    table.append_data_rows(((i[0], i[1]),))
                table.caption.set_style({
                    'font-size': '15px',
                })
                table.set_style({
                    'border-collapse': 'collapse',
                    'word-break': 'keep-all',
                    'white-space': 'nowrap',
                    'font-size': '14px',
                })
                table.set_cell_style({
                    'border-color': '#000',
                    'border-width': '1px',
                    'border-style': 'solid',
                    'padding': '5px',
                })
                html = table.to_html()
                ip = self.get_ip(self.localSocket)
                self.logger.info("DatacombinHtml %s" % ip)
                ref = '''<body><a href="http://%s">enter test web!!!</a></body>''' % (
                    ip + ":9090")
                return html+ref
            return False
        else:
            return data

    def checkFailAndSend(self, pub):
        self.logger.info("checkFailAndSend %s" % pub)
        htmlData = ""
        state = "IDLE"
        # /*+\new\czm\2021.06.03\修改实现*/
        # for imei in self.imeiList:
        for imei in self.deviceInfo.keys():
        # /*+\new\czm\2021.06.03\修改实现*/
            res = requests.get("http://127.0.0.1:9090/error/%s" % imei)
            if res.status_code == 200:
                if len(res.text) > 0:
                    dictS = json.loads(res.text)
                    self.logger.info("checkFailAndSend %s" % (str(dictS)))
                    if ("status" in dictS) and ("results" in dictS):
                        state = dictS["status"]
                        if len(dictS["results"]) > 0:
                            html = self.DatacombinHtml(dictS["results"])
                            if html:
                                htmlData = html

                self.testInfoDict["imei"] = imei
                self.testInfoDict["status"] = state
                self.testInfoDict["htmlData"] = htmlData
                self.on_publish(pub, json.dumps(self.testInfoDict), 1)

    def GetDevBusUsbList(self):
        executecommand = subprocess.Popen("lsusb", shell=True, stdin=subprocess.PIPE, stderr=subprocess.STDOUT,
                                          stdout=subprocess.PIPE)

        devlist = []
        while True:
            line = executecommand.stdout.readline()
            if len(line) > 0:
                try:
                    line = str(line, encoding="gbk")
                    if line.find("0525:a4a7") != -1:
                        devlist.append("/dev/bus/usb/" +
                                       line[4:7] + "/" + line[15:18])
                except Exception as e:
                    line = str(line)
            else:
                break
        return devlist

    def on_message_come(self, lient, userdata, msg):
        self.logger.info("on_message_come topic: %s" % msg.topic)
        if msg.topic == "airm2m/server_upgradeAndTeststart%s" % self.TestMode:
            try:
                if self.state == self.IDLE:
                    self.state = self.RUN
                    self.logger.info("lens %d", len(msg.payload))
                    data = msg.payload
                    hd = open("recVer.pac", "wb")
                    hd.write(data)
                    hd.close()
                    self.NowTestStartTimer = time.time()
                    md5 = self.getmd5("recVer.pac")
                    self.logger.info("%s %s" % (md5, self.MD5))
                    if str(md5) != str(self.MD5):
                        self.logger.info("md5 err")
                        self.on_publish(
                            "airm2m/cilent_UpgradeAndTestCnf%s" % self.TestMode, "MD5校验不对", 1)

                        return
                    else:
                        self.logger.info("ok")
                        atlist = self.execute_command("get_device_list")
                        if atlist and len(atlist) > 0:
                            for i in atlist:
                                self.logger.info("send %s to downlaod" % i)
                                self.set_mode_download(i)
                        time.sleep(4)
                        downlodList = self.execute_command("get_download_list")
                        downloadFlag = False
                        self.logger.info("downlodList 0")
                        if downlodList and len(downlodList) > 0:
                            self.logger.info("downlodList 1")
                            # asyncio.run(self.handle_download(downlodList))
                            self.handle_download(downlodList)
                            # for i in downlodList:
                            #     for j in range(0,3):
                            #         testFlag =  self.execute_command("download_mode", dev=i)
                            #         if testFlag :
                            #             break
                            time.sleep(2)
                            devlist = self.GetDevBusUsbList()
                            for i in devlist:
                                os.system("./usbreset " + i)
                            
                            self.logger.info("downlodList 2")
                        # /*+\new\czm\2021.06.03\下载结束后，等待4s，查询所有接入端口*/
                        time.sleep(4)
                        atlist = self.execute_command("get_device_list")
                        if len(downlodList)!=len(atlist):
                            downloadFlag = False
                        else:
                            downloadFlag = True
                        self.logger.info("downlodList 0")
                        self.logger.info("共下载%d个模块，正常开机%d个模块，状态:%d"%(len(downlodList),len(atlist),downloadFlag))
                        # /*-\new\czm\2021.06.03\下载结束后，等待4s，查询所有接入端口*/
                        if downloadFlag:
                            # /*+\new\czm\2021.06.03\查询设备imei*/
                            if atlist and len(atlist) > 0:
                                for i in atlist:
                                    self.logger.info("send %s to downlaod" % i)
                                    self.get_imei(i)
                            # /*-\new\czm\2021.06.03\查询设备imei*/
                            self.startTest(self.TestMode)
                        else:
                            Exception("download err")
                        self.on_publish(
                            "airm2m/cilent_UpgradeAndTestCnf%s" % self.TestMode, "烧录成功,开始测试", 1)

                else:
                    self.on_publish("airm2m/cilent_UpgradeAndTestCnf%s" %
                                    self.TestMode, "测试服务器不处于IDLE 状态", 1)
            except Exception as e:
                self.on_publish("airm2m/cilent_UpgradeAndTestCnf%s" %
                                self.TestMode, str(e), 1)
                self.state = self.IDLE
        elif msg.topic == "airm2m/server_upgradeAndTestVer%s" % self.TestMode:
            self.logger.info("test ver  %s" % (msg.payload))
            self.testVer = str(msg.payload, encoding="gbk")
            pass
        elif msg.topic == "airm2m/server_upgradeAndTestMD5%s" % self.TestMode:
            self.MD5 = str(msg.payload, encoding="gbk")
            self.logger.info("get net md5 %s" % (self.MD5))
        elif msg.topic == "airm2m/server_endreq%s" % self.TestMode:
            if self.state != self.IDLE:
                self.checkFailAndSend("airm2m/cilent_endCnf%s" % self.TestMode)
                self.killExecuteTest()
                self.state = self.IDLE
        elif msg.topic == "airm2m/server_circulatereq%s" % self.TestMode:
            if self.state != self.IDLE:
                self.checkFailAndSend(
                    "airm2m/cilent_circulateCnf%s" % self.TestMode)
        elif msg.topic == "airm2m/server_stopreq%s" % self.TestMode:
            if self.state != self.IDLE:
                # self.killExecuteTest()
                self.state = self.IDLE
                self.on_publish("airm2m/cilent_StopCnf%s" %
                                self.TestMode, "0", 1)

            pass

        # print(msg.topic + " " + ":" + str(msg.payload))

    def killExecuteTest(self):
        if self.executeTest:
            os.system("kill  %d" % self.executeTest)
            self.executeTest.join()
            self.executeTest = None

    def send_at(self, h_serial, at, back_data=False, timeout=20):
        start = time.perf_counter()
        timeout = timeout
        h_serial.flushInput()
        h_serial.flushOutput()
        read = ""
        h_serial.write((at + "\r\n").encode("gbk"))
        self.logger.info("send at:" + str(at) + "\r\n")
        while True:
            read = read + str(h_serial.read(), encoding='gbk')
            if read.find("OK") != -1:
                if back_data:
                    if read.find(back_data) != -1:
                        return True
                    else:
                        return False
                else:
                    return read

            if time.perf_counter() > timeout + start:
                raise Exception("time out")

    def runTest(self, type):
        self.logger.info("runTest %s" % type)

        nowPath = os.getcwd()
        os.chdir(self.TesterPath)
        os.system("git pull")
        os.system("sudo python3 main.py -v %s > log.txt" % self.testVer)
        # time.sleep(10000)
        os.chdir(nowPath)
        self.logger.info("%s runTest end " % self.TestMode)

        print('子进程已结束')

    def startTest(self, type):
        # if type == "AT":
        # self.executeTest = threading.Thread(target=self.runTest, args=(type,))
        # self.executeTest.start()
        if self.testWeb:
            self.testWeb.join()
        print('父进程 %d.' % os.getpid())
        self.executeTest = os.getpid()
        execute = Process(target=self.runTest, args=(type,))
        print('子进程将要执行')
        execute.start()

        self.logger.info("%s startTesting" % self.TestMode)

    def execute_command_asn(self,  dev):
        print('start execute_command_asn cmd...')
        cmd = self.sprd_dloader + " %s -reset 1" % (dev)

        print("execute_command  over: %s" % cmd)
        # executecommand = subprocess.Popen(cmd, shell=True, stdin=subprocess.PIPE, stderr=subprocess.STDOUT,
        #                                   stdout=subprocess.PIPE)
        # # time.sleep(1)
        # hd = threading.Timer(
        #     80, self.execute_command_asn_timeout, args=(executecommand, dev))
        # hd.start()
        # while True:
        #     # await asyncio.sleep(0.1)
        #     time.sleep(0.1)
        #     line = executecommand.stdout.readline()
        #     if len(line) > 0:
        #         try:
        #             line = str(line, encoding="gbk")
        #             print("from %s" % dev, end=":")
        #             print(line)
        #             if line.find("download success") != -1:
        #                 # print("%s Download sucess" % dev)
        #                 time.sleep(1)
        #                 executecommand.kill()
        #                 hd.cancel()
        #                 return True

        #         except Exception as e:
        #             # line = str(line)
        #             pass
        #     else:
        #         break

        # executecommand.kill()
        # executecommand = False
        # hd.cancel()
        # /*+\new\czm\2021.06.03\下载卡死时无法退出进程*/
        executecommand = subprocess.Popen(cmd, shell=True) #, stdin=subprocess.PIPE, stderr=subprocess.PIPE,stdout=subprocess.PIPE)
        try:
            executecommand.wait(timeout = 55)# 55秒没下载完，r
            return True
        except Exception as e:
            print("dev:%s,下载超时"%(dev))
        # /*-\new\czm\2021.06.03\下载卡死时无法退出进程*/
            executecommand.kill()
            return False


    def handle_download(self, portList):
        self.logger.info("downlodList 3")
        # /*+\new\czm\2021.06.02\多协程下载有较大概率导致工具卡死，原因未知。改成线程*/
        # tasks = [asyncio.Task(self.execute_command_asn(i)) for i in portList]
        # await asyncio.gather(*tasks)
        threads = []
        for i in portList:
            t = threading.Thread(target=self.execute_command_asn, args=(i,))
            t.start()
            threads.append(t)

        while True:
            if len(threads) == 0:
                break
            for t in threads:
                if t.is_alive():
                    # print("czm test ",t,len(threads))
                    time.sleep(1)
                else:
                    threads.remove(t)

        self.logger.info("downlodList 4")
        # /*-\new\czm\2021.06.02\多协程下载有较大概率导致工具卡死，原因未知。改成线程*/

    def execute_command(self, type, dev=False):
        self.logger.info('start executing cmd...')
        portList = []
        if type == "get_device_list":
            cmd = "%s %s" % (self.diag, "1782 4e00 3 0")
        elif type == "get_download_list":
            cmd = "%s %s" % (self.diag, "0525 a4a7  0 1")
        elif type == "download_mode":
            cmd = self.sprd_dloader + " %s -reset 1" % (dev)
        self.logger.info("execute_command: %s" % cmd)
        self.executecommand = subprocess.Popen(cmd, shell=True, stdin=subprocess.PIPE, stderr=subprocess.STDOUT,
                                               stdout=subprocess.PIPE)
        line = self.executecommand.stdout.readline()
        while line:
            line = self.executecommand.stdout.readline()
            if len(line) > 0:
                try:
                    line = str(line, encoding="gbk")
                    self.logger.info(line)
                    if type == "get_device_list":
                        gb = re.match("AT (.*)", line)
                        if gb:
                            self.logger.info("find AT port %s" % gb.group(1))
                            portList.append("/dev/%s" % gb.group(1))
                        elif line.find("END") != -1:
                            break
                    elif type == "get_download_list":
                        gb = re.match("Download (.*)", line)
                        if gb:
                            self.logger.info(
                                "find Download port %s" % gb.group(1))
                            portList.append("/dev/%s" % gb.group(1))
                        elif line.find("END") != -1:
                            break

                    if line.find("download success") != -1:
                        self.logger.info("%s Download sucess" % dev)
                        self.killExecuteTest()
                        return True

                except Exception as e:
                    line = str(line)
            else:
                break

        self.executecommand.kill()
        self.executecommand = False
        if type == "get_device_list" or type == "get_download_list":
            if len(portList) > 0:
                return portList
            else:
                return False
        else:
            return False

    def on_subscribe(self):
        self.mqttClient.subscribe(
            "airm2m/server_upgradeAndTestVer%s" % self.TestMode, 1)
        self.mqttClient.subscribe(
            "airm2m/server_upgradeAndTestMD5%s" % self.TestMode, 1)
        self.mqttClient.subscribe(
            "airm2m/server_upgradeAndTeststart%s" % self.TestMode, 1)
        self.mqttClient.subscribe("airm2m/server_endreq%s" % self.TestMode, 1)
        self.mqttClient.subscribe(
            "airm2m/server_circulatereq%s" % self.TestMode, 1)
        self.mqttClient.subscribe("airm2m/server_stopreq%s" % self.TestMode, 1)
        self.mqttClient.subscribe("airm2m/test_fail%s" % self.TestMode, 1)

        self.mqttClient.on_message = self.on_message_come

    def startMqtt(self):
        #MQTTHOST = "192.168.39.233"
        #MQTTPORT = 1883
        MQTTHOST = "36.7.87.100"
        MQTTPORT = 3008
        self.mqttClient = mqtt.Client()
        self.mqttClient.connect(MQTTHOST, MQTTPORT, 60)
        self.mqttClient.loop_start()
        pass

    def on_publish(self, topic, mes, qos):
        self.mqttClient.publish(topic, mes, qos)

        pass

    def run(self):
        self.logger.info("start %s client" % self.TestMode)
        self.startMqtt()
        self.on_subscribe()
        while(True):
            global is_sigint_up
            if is_sigint_up:
                self.logger.info("CTRL+C lj")
                self.killExecuteTest()

                is_sigint_up = False
                sys.exit()

            time.sleep(2)
    # /*+\new\czm\2021.06.02\单独测试下载用的函数，节省调试时间*/

    def testdownload(self):
        self.logger.info("ok")
        atlist = self.execute_command("get_device_list")
        if atlist and len(atlist) > 0:
            for i in atlist:
                self.logger.info("send %s to downlaod" % i)
                self.set_mode_download(i)
        time.sleep(4)
        downlodList = self.execute_command("get_download_list")
        testFlag = False
        self.logger.info("downlodList 0")
        if downlodList and len(downlodList) > 0:
            self.logger.info("downlodList 1")
            self.handle_download(downlodList)
            # for i in downlodList:
            #     for j in range(0,3):
            #         testFlag =  self.execute_command("download_mode", dev=i)
            #         if testFlag :
            #             break
            time.sleep(2)
            devlist = self.GetDevBusUsbList()
            for i in devlist:
                os.system("./usbreset " + i)
            self.logger.info("downlodList 2")
        

            time.sleep(4)
            atlist = self.execute_command("get_device_list")
            if len(downlodList)!=len(atlist):
                downloadFlag = False
            else:
                downloadFlag = True
            self.logger.info("downlodList 0")
            self.logger.info("共下载%d个模块，正常开机%d个模块，状态:%d"%(len(downlodList),len(atlist),downloadFlag))

            if downloadFlag:
                if atlist and len(atlist) > 0:
                    for i in atlist:
                        self.get_imei(i)

            print(self.deviceInfo)
                # self.startTest(self.TestMode)

    # /*-\new\czm\2021.06.02\单独测试下载用的函数，节省调试时间*/


if __name__ == '__main__':
    handle = mqttRun()
    handle.run()
    # handle.testdownload()


# if __name__ == '__main__':
#     handle = mqttRun()
#     hd = open("stdout.bin", "rb")
#     data = hd.read()
#     hd.close()
#     html = handle.DatacombinHtml(data)
